// Callout syntax parser for markdown-it
// Callout syntax: > [!NOTE] This is a note

import MarkdownIt from 'markdown-it';
import Token from 'markdown-it/lib/token';

export const DEFAULT_OBSIDIAN_ICONS: Record<string, string> = {
  abstract:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-clipboard-list"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg>',
  bug: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-bug"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>',
  danger:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
  example:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-list"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>',
  failure:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
  info: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
  note: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-pencil"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>',
  question:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-help-circle"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>',
  quote:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-quote"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg>',
  success:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>',
  tip: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-flame"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>',
  todo: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-check-circle-2"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
  warning:
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-alert-triangle"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
};

// 别名
DEFAULT_OBSIDIAN_ICONS.attention = DEFAULT_OBSIDIAN_ICONS.warning;
DEFAULT_OBSIDIAN_ICONS.caution = DEFAULT_OBSIDIAN_ICONS.warning;
DEFAULT_OBSIDIAN_ICONS.check = DEFAULT_OBSIDIAN_ICONS.success;
DEFAULT_OBSIDIAN_ICONS.cite = DEFAULT_OBSIDIAN_ICONS.quote;
DEFAULT_OBSIDIAN_ICONS.done = DEFAULT_OBSIDIAN_ICONS.success;
DEFAULT_OBSIDIAN_ICONS.error = DEFAULT_OBSIDIAN_ICONS.danger;
DEFAULT_OBSIDIAN_ICONS.fail = DEFAULT_OBSIDIAN_ICONS.failure;
DEFAULT_OBSIDIAN_ICONS.faq = DEFAULT_OBSIDIAN_ICONS.question;
DEFAULT_OBSIDIAN_ICONS.help = DEFAULT_OBSIDIAN_ICONS.question;
DEFAULT_OBSIDIAN_ICONS.hint = DEFAULT_OBSIDIAN_ICONS.tip;
DEFAULT_OBSIDIAN_ICONS.important = DEFAULT_OBSIDIAN_ICONS.tip;
DEFAULT_OBSIDIAN_ICONS.missing = DEFAULT_OBSIDIAN_ICONS.failure;
DEFAULT_OBSIDIAN_ICONS.summary = DEFAULT_OBSIDIAN_ICONS.abstract;
DEFAULT_OBSIDIAN_ICONS.tldr = DEFAULT_OBSIDIAN_ICONS.abstract;

const callout = /^\[!([^\]]+)\](\+|-|) *(.*)? */;
const admonition = /^ad-([^\s]+) */;
const admonitionHeader = /^(title|collapse|icon|color):(.*)/;
const headerToAttr: Record<string, string> = {
  title: 'data-callout-title',
  icon: 'data-callout-icon',
  color: 'data-callout-color',
};

function inspectFencedCodeContent(
  tokens: Token[],
  startIdx: number,
  md: MarkdownIt,
  options,
) {
  const token = tokens[startIdx];
  if (!token.info) {
    return '';
  }
  const match = token.info
    .replace(options.langPrefix || '', '')
    .match(admonition);
  if (match) {
    token.type = 'admonition_block';
    token.attrPush(['class', 'callout']);
    token.attrPush(['data-callout', match[1].toLowerCase()]);

    let lines = token.content.split('\n');
    while (lines.length > 0 && admonitionHeader.test(lines[0])) {
      const match = lines[0].match(admonitionHeader);
      if (match) {
        const attrName = headerToAttr[match[1].trim().toLowerCase()];
        if (attrName) {
          token.attrPush([attrName, match[2].trim()]);
        }
        lines = lines.slice(1);
      } else {
        break;
      }
    }
    token.content = md.render(lines.join('\n'), {});
  }
}

function inspectBlockquoteContent(iterable: Token[], startIdx: number) {
  let content = '';
  let blockquoteDepth = 0;
  let endIdx = startIdx;
  let contentIdx = startIdx;

  for (let i = startIdx; i < iterable.length; i++) {
    const token = iterable[i];
    if (token.type === 'blockquote_open') {
      blockquoteDepth++;
    } else if (token.type === 'blockquote_close') {
      endIdx = i;
      blockquoteDepth--;
    }
    if (blockquoteDepth === 0) {
      break;
    } else if (blockquoteDepth > 1) {
      continue;
    }
    if (token.type === 'inline') {
      if (contentIdx === startIdx && token.content.match(callout)) {
        contentIdx = i;
      }
      content = content + token.content;
    } else if (token.type === 'paragraph_close') {
      content += '\n';
    }
  }
  const match = content.match(callout);
  if (match && startIdx !== endIdx) {
    const calloutType = match[1].toLowerCase();
    const calloutFold = match[2];
    const calloutTitle = match[3];

    iterable[startIdx].type = 'callout_open';
    iterable[startIdx].attrPush(['class', 'callout']);
    iterable[startIdx].attrPush(['data-callout', calloutType]);
    iterable[startIdx].attrPush(['data-callout-fold', calloutFold]);
    if (calloutTitle) {
      iterable[startIdx].attrPush(['data-callout-title', calloutTitle]);
    }
    iterable[endIdx].type = 'callout_close';
    iterable[endIdx].attrPush(['data-callout', calloutType]);
    iterable[endIdx].attrPush(['data-callout-fold', calloutFold]);
    if (
      contentIdx !== startIdx &&
      iterable[contentIdx] &&
      iterable[contentIdx].children
    ) {
      iterable[contentIdx].content = iterable[contentIdx].content
        .replace(callout, '')
        .trim();
    }
  }
}

function renderCalloutPrefix(token: Token, md: MarkdownIt, options) {
  const fold_icon =
    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide chevron-right"><path d="m9 18 6-6-6-6"></path></svg>';
  const callout = token.attrGet('data-callout');
  const fold = token.attrGet('data-callout-fold');
  if (callout && fold) {
    return `\n<details class="callout" data-callout="${callout}" data-callout-fold="${fold}"${fold === '+' ? ' open' : ''}>\n<summary class="callout-title">\n<div class="callout-icon">\n${getIcon(token, options)}\n</div>\n<div class="callout-title-inner">${getTitle(token, md)}</div>\n<div class="callout-fold">${fold_icon}</div>\n</summary>\n<div class="callout-content">`;
  } else if (callout) {
    return `\n<div class="callout" data-callout="${callout}">\n<div class="callout-title">\n<div class="callout-icon">\n${getIcon(token, options)}\n</div>\n<div class="callout-title-inner">${getTitle(token, md)}</div>\n</div>\n<div class="callout-content">`;
  }
  return '';
}

function renderCalloutPostfix(token: Token) {
  const callout = token.attrGet('data-callout');
  const fold = token.attrGet('data-callout-fold');
  if (callout && fold) {
    return '</div></details>';
  } else if (callout) {
    return '</div></div>';
  }
  return '';
}

function getIcon(token: Token, options) {
  const icon = token.attrGet('data-callout-icon');
  if (icon) {
    return icon.trim();
  }
  const callout = token.attrGet('data-callout');
  if (callout) {
    return (
      options.icons?.[callout] ||
      DEFAULT_OBSIDIAN_ICONS[callout] ||
      DEFAULT_OBSIDIAN_ICONS.note
    );
  }
  return '';
}

function getTitle(token: Token, md: MarkdownIt) {
  const title = token.attrGet('data-callout-title');
  if (title) {
    return md.renderInline(title.trim());
  }
  const callout = token.attrGet('data-callout');
  if (callout) {
    return toTitleCase(callout);
  }
  return '';
}

function toTitleCase(str: string): string {
  return str
    .split(' ')
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
    .join(' ');
}

function calloutPlugin(md: MarkdownIt, options): void {
  md.core.ruler.after('block', 'obsidian-callouts', (state) => {
    const tokens = state.tokens;
    for (let i = 0; i < tokens.length; i++) {
      const token = tokens[i];
      if (token.type === 'blockquote_open') {
        inspectBlockquoteContent(tokens, i);
      }
      if (token.type === 'fence') {
        inspectFencedCodeContent(tokens, i, md, options);
      }
    }
  });

  md.renderer.rules.callout_open = (tokens: Token[], idx: number) => {
    const token = tokens[idx];
    return renderCalloutPrefix(token, md, options);
  };

  md.renderer.rules.admonition_block = (tokens: Token[], idx: number) => {
    const token = tokens[idx];
    return `${renderCalloutPrefix(token, md, options)}${token.content}\n</div>\n</div>`;
  };

  md.renderer.rules.callout_close = (tokens: Token[], idx: number) => {
    const token = tokens[idx];
    return renderCalloutPostfix(token);
  };
}

export default calloutPlugin;
